/*-------------------------------------------------------------------------
 *
 * mac8.c
 *      PostgreSQL type definitions for 8 byte (EUI-64) MAC addresses.
 *
 * EUI-48 (6 byte) MAC addresses are accepted as input and are stored in
 * EUI-64 format, with the 4th and 5th bytes set to FF and FE, respectively.
 *
 * Output is always in 8 byte (EUI-64) format.
 *
 * The following code is written with the assumption that the OUI field
 * size is 24 bits.
 *
 * Portions Copyright (c) 1998-2017, PostgreSQL Global Development Group
 *
 * This source code file contains modifications made by THL A29 Limited ("Tencent Modifications").
 * All Tencent Modifications are Copyright (C) 2023 THL A29 Limited.
 *
 * IDENTIFICATION
 *          src/backend/utils/adt/mac8.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/hash.h"
#include "libpq/pqformat.h"
#include "utils/builtins.h"
#include "utils/inet.h"

/*
 *    Utility macros used for sorting and comparing:
 */
#define hibits(addr) \
  ((unsigned long)(((addr)->a<<24) | ((addr)->b<<16) | ((addr)->c<<8) | ((addr)->d)))

#define lobits(addr) \
  ((unsigned long)(((addr)->e<<24) | ((addr)->f<<16) | ((addr)->g<<8) | ((addr)->h)))

static unsigned char hex2_to_uchar(const unsigned char *str, const unsigned char *ptr);

static const signed char hexlookup[128] = {
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
    -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
};

/*
 * hex2_to_uchar - convert 2 hex digits to a byte (unsigned char)
 *
 * This will ereport() if the end of the string is reached ('\0' found), or if
 * either character is not a valid hex digit.
 *
 * ptr is the pointer to where the digits to convert are in the string, str is
 * the entire string, which is used only for error reporting.
 */
static inline unsigned char
hex2_to_uchar(const unsigned char *ptr, const unsigned char *str)
{
    unsigned char ret = 0;
    signed char lookup;

    /* Handle the first character */
    if (*ptr > 127)
        goto invalid_input;

    lookup = hexlookup[*ptr];
    if (lookup < 0)
        goto invalid_input;

    ret = lookup << 4;

    /* Move to the second character */
    ptr++;

    if (*ptr > 127)
        goto invalid_input;

    lookup = hexlookup[*ptr];
    if (lookup < 0)
        goto invalid_input;

    ret += lookup;

    return ret;

invalid_input:
    ereport(ERROR,
            (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
             errmsg("invalid input syntax for type %s: \"%s\"", "macaddr8",
                    str)));

    /* We do not actually reach here */
    return 0;
}

/*
 * MAC address (EUI-48 and EUI-64) reader. Accepts several common notations.
 */
Datum
macaddr8_in(PG_FUNCTION_ARGS)
{// #lizard forgives
    const unsigned char *str = (unsigned char *) PG_GETARG_CSTRING(0);
    const unsigned char *ptr = str;
    macaddr8   *result;
    unsigned char a = 0,
                b = 0,
                c = 0,
                d = 0,
                e = 0,
                f = 0,
                g = 0,
                h = 0;
    int            count = 0;
    unsigned char spacer = '\0';

    /* skip leading spaces */
    while (*ptr && isspace(*ptr))
        ptr++;

    /* digits must always come in pairs */
    while (*ptr && *(ptr + 1))
    {
        /*
         * Attempt to decode each byte, which must be 2 hex digits in a row.
         * If either digit is not hex, hex2_to_uchar will throw ereport() for
         * us.  Either 6 or 8 byte MAC addresses are supported.
         */

        /* Attempt to collect a byte */
        count++;

        switch (count)
        {
            case 1:
                a = hex2_to_uchar(ptr, str);
                break;
            case 2:
                b = hex2_to_uchar(ptr, str);
                break;
            case 3:
                c = hex2_to_uchar(ptr, str);
                break;
            case 4:
                d = hex2_to_uchar(ptr, str);
                break;
            case 5:
                e = hex2_to_uchar(ptr, str);
                break;
            case 6:
                f = hex2_to_uchar(ptr, str);
                break;
            case 7:
                g = hex2_to_uchar(ptr, str);
                break;
            case 8:
                h = hex2_to_uchar(ptr, str);
                break;
            default:
                /* must be trailing garbage... */
                ereport(ERROR,
                        (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                         errmsg("invalid input syntax for type %s: \"%s\"", "macaddr8",
                                str)));
        }

        /* Move forward to where the next byte should be */
        ptr += 2;

        /* Check for a spacer, these are valid, anything else is not */
        if (*ptr == ':' || *ptr == '-' || *ptr == '.')
        {
            /* remember the spacer used, if it changes then it isn't valid */
            if (spacer == '\0')
                spacer = *ptr;

            /* Have to use the same spacer throughout */
            else if (spacer != *ptr)
                ereport(ERROR,
                        (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                         errmsg("invalid input syntax for type %s: \"%s\"", "macaddr8",
                                str)));

            /* move past the spacer */
            ptr++;
        }

        /* allow trailing whitespace after if we have 6 or 8 bytes */
        if (count == 6 || count == 8)
        {
            if (isspace(*ptr))
            {
                while (*++ptr && isspace(*ptr));

                /* If we found a space and then non-space, it's invalid */
                if (*ptr)
                    ereport(ERROR,
                            (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                             errmsg("invalid input syntax for type %s: \"%s\"", "macaddr8",
                                    str)));
            }
        }
    }

    /* Convert a 6 byte MAC address to macaddr8 */
    if (count == 6)
    {
        h = f;
        g = e;
        f = d;

        d = 0xFF;
        e = 0xFE;
    }
    else if (count != 8)
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                 errmsg("invalid input syntax for type %s: \"%s\"", "macaddr8",
                        str)));

    result = (macaddr8 *) palloc0(sizeof(macaddr8));

    result->a = a;
    result->b = b;
    result->c = c;
    result->d = d;
    result->e = e;
    result->f = f;
    result->g = g;
    result->h = h;

    PG_RETURN_MACADDR8_P(result);
}

/*
 * MAC8 address (EUI-64) output function. Fixed format.
 */
Datum
macaddr8_out(PG_FUNCTION_ARGS)
{
    macaddr8   *addr = PG_GETARG_MACADDR8_P(0);
    char       *result;

    result = (char *) palloc(32);

    snprintf(result, 32, "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
             addr->a, addr->b, addr->c, addr->d,
             addr->e, addr->f, addr->g, addr->h);

    PG_RETURN_CSTRING(result);
}

/*
 * macaddr8_recv - converts external binary format(EUI-48 and EUI-64) to macaddr8
 *
 * The external representation is just the eight bytes, MSB first.
 */
Datum
macaddr8_recv(PG_FUNCTION_ARGS)
{
    StringInfo    buf = (StringInfo) PG_GETARG_POINTER(0);
    macaddr8   *addr;

    addr = (macaddr8 *) palloc0(sizeof(macaddr8));

    addr->a = pq_getmsgbyte(buf);
    addr->b = pq_getmsgbyte(buf);
    addr->c = pq_getmsgbyte(buf);

    if (buf->len == 6)
    {
        addr->d = 0xFF;
        addr->e = 0xFE;
    }
    else
    {
        addr->d = pq_getmsgbyte(buf);
        addr->e = pq_getmsgbyte(buf);
    }

    addr->f = pq_getmsgbyte(buf);
    addr->g = pq_getmsgbyte(buf);
    addr->h = pq_getmsgbyte(buf);

    PG_RETURN_MACADDR8_P(addr);
}

/*
 * macaddr8_send - converts macaddr8(EUI-64) to binary format
 */
Datum
macaddr8_send(PG_FUNCTION_ARGS)
{
    macaddr8   *addr = PG_GETARG_MACADDR8_P(0);
    StringInfoData buf;

    pq_begintypsend(&buf);
    pq_sendbyte(&buf, addr->a);
    pq_sendbyte(&buf, addr->b);
    pq_sendbyte(&buf, addr->c);
    pq_sendbyte(&buf, addr->d);
    pq_sendbyte(&buf, addr->e);
    pq_sendbyte(&buf, addr->f);
    pq_sendbyte(&buf, addr->g);
    pq_sendbyte(&buf, addr->h);

    PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
}


/*
 * macaddr8_cmp_internal - comparison function for sorting:
 */
static int32
macaddr8_cmp_internal(macaddr8 *a1, macaddr8 *a2)
{
    if (hibits(a1) < hibits(a2))
        return -1;
    else if (hibits(a1) > hibits(a2))
        return 1;
    else if (lobits(a1) < lobits(a2))
        return -1;
    else if (lobits(a1) > lobits(a2))
        return 1;
    else
        return 0;
}

Datum
macaddr8_cmp(PG_FUNCTION_ARGS)
{
    macaddr8   *a1 = PG_GETARG_MACADDR8_P(0);
    macaddr8   *a2 = PG_GETARG_MACADDR8_P(1);

    PG_RETURN_INT32(macaddr8_cmp_internal(a1, a2));
}

/*
 * Boolean comparison functions.
 */

Datum
macaddr8_lt(PG_FUNCTION_ARGS)
{
    macaddr8   *a1 = PG_GETARG_MACADDR8_P(0);
    macaddr8   *a2 = PG_GETARG_MACADDR8_P(1);

    PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) < 0);
}

Datum
macaddr8_le(PG_FUNCTION_ARGS)
{
    macaddr8   *a1 = PG_GETARG_MACADDR8_P(0);
    macaddr8   *a2 = PG_GETARG_MACADDR8_P(1);

    PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) <= 0);
}

Datum
macaddr8_eq(PG_FUNCTION_ARGS)
{
    macaddr8   *a1 = PG_GETARG_MACADDR8_P(0);
    macaddr8   *a2 = PG_GETARG_MACADDR8_P(1);

    PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) == 0);
}

Datum
macaddr8_ge(PG_FUNCTION_ARGS)
{
    macaddr8   *a1 = PG_GETARG_MACADDR8_P(0);
    macaddr8   *a2 = PG_GETARG_MACADDR8_P(1);

    PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) >= 0);
}

Datum
macaddr8_gt(PG_FUNCTION_ARGS)
{
    macaddr8   *a1 = PG_GETARG_MACADDR8_P(0);
    macaddr8   *a2 = PG_GETARG_MACADDR8_P(1);

    PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) > 0);
}

Datum
macaddr8_ne(PG_FUNCTION_ARGS)
{
    macaddr8   *a1 = PG_GETARG_MACADDR8_P(0);
    macaddr8   *a2 = PG_GETARG_MACADDR8_P(1);

    PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) != 0);
}

/*
 * Support function for hash indexes on macaddr8.
 */
Datum
hashmacaddr8(PG_FUNCTION_ARGS)
{
    macaddr8   *key = PG_GETARG_MACADDR8_P(0);

    return hash_any((unsigned char *) key, sizeof(macaddr8));
}

Datum
hashmacaddr8extended(PG_FUNCTION_ARGS)
{
	macaddr8   *key = PG_GETARG_MACADDR8_P(0);

	return hash_any_extended((unsigned char *) key, sizeof(macaddr8),
							 PG_GETARG_INT64(1));
}

/*
 * Arithmetic functions: bitwise NOT, AND, OR.
 */
Datum
macaddr8_not(PG_FUNCTION_ARGS)
{
    macaddr8   *addr = PG_GETARG_MACADDR8_P(0);
    macaddr8   *result;

    result = (macaddr8 *) palloc0(sizeof(macaddr8));
    result->a = ~addr->a;
    result->b = ~addr->b;
    result->c = ~addr->c;
    result->d = ~addr->d;
    result->e = ~addr->e;
    result->f = ~addr->f;
    result->g = ~addr->g;
    result->h = ~addr->h;

    PG_RETURN_MACADDR8_P(result);
}

Datum
macaddr8_and(PG_FUNCTION_ARGS)
{
    macaddr8   *addr1 = PG_GETARG_MACADDR8_P(0);
    macaddr8   *addr2 = PG_GETARG_MACADDR8_P(1);
    macaddr8   *result;

    result = (macaddr8 *) palloc0(sizeof(macaddr8));
    result->a = addr1->a & addr2->a;
    result->b = addr1->b & addr2->b;
    result->c = addr1->c & addr2->c;
    result->d = addr1->d & addr2->d;
    result->e = addr1->e & addr2->e;
    result->f = addr1->f & addr2->f;
    result->g = addr1->g & addr2->g;
    result->h = addr1->h & addr2->h;

    PG_RETURN_MACADDR8_P(result);
}

Datum
macaddr8_or(PG_FUNCTION_ARGS)
{
    macaddr8   *addr1 = PG_GETARG_MACADDR8_P(0);
    macaddr8   *addr2 = PG_GETARG_MACADDR8_P(1);
    macaddr8   *result;

    result = (macaddr8 *) palloc0(sizeof(macaddr8));
    result->a = addr1->a | addr2->a;
    result->b = addr1->b | addr2->b;
    result->c = addr1->c | addr2->c;
    result->d = addr1->d | addr2->d;
    result->e = addr1->e | addr2->e;
    result->f = addr1->f | addr2->f;
    result->g = addr1->g | addr2->g;
    result->h = addr1->h | addr2->h;

    PG_RETURN_MACADDR8_P(result);
}

/*
 * Truncation function to allow comparing macaddr8 manufacturers.
 */
Datum
macaddr8_trunc(PG_FUNCTION_ARGS)
{
    macaddr8   *addr = PG_GETARG_MACADDR8_P(0);
    macaddr8   *result;

    result = (macaddr8 *) palloc0(sizeof(macaddr8));

    result->a = addr->a;
    result->b = addr->b;
    result->c = addr->c;
    result->d = 0;
    result->e = 0;
    result->f = 0;
    result->g = 0;
    result->h = 0;

    PG_RETURN_MACADDR8_P(result);
}

/*
 * Set 7th bit for modified EUI-64 as used in IPv6.
 */
Datum
macaddr8_set7bit(PG_FUNCTION_ARGS)
{
    macaddr8   *addr = PG_GETARG_MACADDR8_P(0);
    macaddr8   *result;

    result = (macaddr8 *) palloc0(sizeof(macaddr8));

    result->a = addr->a | 0x02;
    result->b = addr->b;
    result->c = addr->c;
    result->d = addr->d;
    result->e = addr->e;
    result->f = addr->f;
    result->g = addr->g;
    result->h = addr->h;

    PG_RETURN_MACADDR8_P(result);
}

/*----------------------------------------------------------
 *    Conversion operators.
 *---------------------------------------------------------*/

Datum
macaddrtomacaddr8(PG_FUNCTION_ARGS)
{
    macaddr    *addr6 = PG_GETARG_MACADDR_P(0);
    macaddr8   *result;

    result = (macaddr8 *) palloc0(sizeof(macaddr8));

    result->a = addr6->a;
    result->b = addr6->b;
    result->c = addr6->c;
    result->d = 0xFF;
    result->e = 0xFE;
    result->f = addr6->d;
    result->g = addr6->e;
    result->h = addr6->f;


    PG_RETURN_MACADDR8_P(result);
}

Datum
macaddr8tomacaddr(PG_FUNCTION_ARGS)
{
    macaddr8   *addr = PG_GETARG_MACADDR8_P(0);
    macaddr    *result;

    result = (macaddr *) palloc0(sizeof(macaddr));

    if ((addr->d != 0xFF) || (addr->e != 0xFE))
        ereport(ERROR,
                (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
                 errmsg("macaddr8 data out of range to convert to macaddr"),
                 errhint("Only addresses that have FF and FE as values in the "
                         "4th and 5th bytes, from the left, for example: "
                         "XX-XX-XX-FF-FE-XX-XX-XX, are eligible to be converted "
                         "from macaddr8 to macaddr.")));

    result->a = addr->a;
    result->b = addr->b;
    result->c = addr->c;
    result->d = addr->f;
    result->e = addr->g;
    result->f = addr->h;

    PG_RETURN_MACADDR_P(result);
}
